﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

using System.Reflection;
using System.Reflection.Emit;
using System.Diagnostics;

namespace SscLib
{
    public class CodeGen
    {
        string exeName;
        AssemblyBuilder assembly;
        ModuleBuilder module;
        SymbolTable symTab;
        TypeBuilder program;
        Context ctx = new Context();

        public Context Context
        {
            get { return this.ctx; }
            set { this.ctx = value; }
        }

        public CodeGen(string filename, SymbolTable symTab)
        {
            this.symTab = symTab;

            this.exeName = Path.ChangeExtension(filename,null);

            AssemblyName assemblyName = new AssemblyName();
            assemblyName.Name = exeName;

            
            assembly =
                AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName,
                                                              AssemblyBuilderAccess.Save);

            module = assembly.DefineDynamicModule(exeName + "Module", exeName + ".exe", true);
        }

        //[Obsolete("Use the type specific version of CreateMetadata for the symbol.", true)]
        public void CreateMetadata(Symbol sym)
        {
            if (sym is VariableSymbol) CreateMetadata((VariableSymbol)sym);
            else if (sym is FieldSymbol) CreateMetadata((FieldSymbol)sym);
            else
                throw new Exception(string.Format("Use the type specific version of CreateMetadata for the symbol ({0}:{1})", sym.Name, sym.GetType().ToString()));
        }

        public void CreateMetadata(FieldSymbol f)
        {
                //case SymbolKind.Kind.Global:
                //    if (sym.type != Tab.noType)
                //        sym.fld = program.DefineField(sym.name, sym.type.sysType, GLOBALATTR);
                //    break;
            TypeSymbol definingType = f.Scope as TypeSymbol;
            if (definingType == null) throw new ArgumentException(string.Format("The field '{0}' is not scoped inside a Type", f.Name), "f");

            if (f.Type == SymbolTable.NoType) Console.WriteLine("CreateMetadata: Field cannot be of type void");
            f.Info = definingType.Builder.DefineField(f.Name, f.SystemType, FieldAttributes.Public);
        }

        public void CreateMetadata(VariableSymbol v)
        {
            //TODO: we should handle blocks and defining methods better instead of this backlooping.
            MethodSymbolBase definingMethod = null;
            ScopeSymbol scope = v.Scope;
            while (scope != null)
            {
                definingMethod = v.Scope as MethodSymbolBase;
                if (v.Scope is TypeSymbol || v.Scope is MethodSymbolBase) break;
                scope = scope.Scope;
            }
            if (definingMethod == null) throw new ArgumentException(string.Format("The variable '{0}' is not scoped inside a Method. Defining Scope is '{1}'.", v.Name, v.Scope.GetType().Name), "v");

            ILGenerator il = definingMethod.ILGenerator;
            LocalBuilder lb = il.DeclareLocal(v.SystemType);

                //    lb.SetLocalSymInfo(sym.name); //, sym.adr, scopeCount);

                //    // Bugfix: Added because of bug of using sym.adr of local variables
                //    // inside blocks. Because of Scope nature all block local variables start
                //    // with index of 0, even if the variable is declared at method scope.
                //    // TODO: check with CLR if it is possible to have scope local variables!
                //    // PS! we could have updated the sym.adr field instead.
            v.Info = lb;
        }

        public void CreateMetadata(ProgramSymbol p)
        {
            ModuleSymbol definingModule = p.Scope as ModuleSymbol;
            if (definingModule == null) throw new ArgumentException(string.Format("The program class '{0}' is not scoped inside a Module", p.Name), "p");

            TypeBuilder tb = module.DefineType(exeName + "." + p.Name,
                    TypeAttributes.Class | TypeAttributes.Public,// | TypeAttributes.BeforeFieldInit, 
                    p.BaseType.SystemType);

            p.SystemType = tb;
            program = tb;
        }

        public void CreateMetadata(TypeSymbol t)
        {
            TypeSymbol definingType = t.Scope as TypeSymbol;
            if (definingType == null) throw new ArgumentException(string.Format("The class '{0}' is not scoped inside a Type", t.Name), "t");

            TypeBuilder defingingTypeBuilder = definingType.Builder;

            TypeBuilder tb = defingingTypeBuilder.DefineNestedType(
                t.Name, 
                TypeAttributes.Class | TypeAttributes.NestedPrivate,
                t.BaseType.SystemType);

            t.SystemType = tb;

                    //inner = inner.DefineNestedType(sym.name,
                    //    TypeAttributes.Class | TypeAttributes.NestedPrivate,
                    //    typeof(SsLib.SimObject));
                    //sym.type.sysType = inner;

                    // define default contructor (calls base constructor)
                    //sym.ctor = inner.DefineConstructor(MethodAttributes.Public,
                    //                                       CallingConventions.Standard, new Type[0]);
                    //il = sym.ctor.GetILGenerator();
                    //il.Emit(LDARG0);
                    //il.Emit(CALL, objCtor);
                    //il.Emit(RET);
        }

        public void CreateArrayMetadata(TypeSymbol arrayType)
        {
            arrayType.SystemType = arrayType.ElementType.SystemType.MakeArrayType(arrayType.Dimentions);
        }

        public void CreateMetadata(CtorSymbol ctor)
        {
            TypeSymbol definingType = ctor.Scope as TypeSymbol;
            if (definingType == null) throw new ArgumentException(string.Format("The constructor '{0}' is not scoped inside a Type", ctor.Name), "ctor");

            Type[] args = new Type[ctor.Args.Count];

            foreach (ArgumentSymbol arg in ctor.Args)
            {
                args[arg.Adr - 1] = arg.SystemType;
            }

            TypeBuilder tb = definingType.Builder;

            MethodAttributes attribs = MethodAttributes.Public;


            ConstructorBuilder builder = tb.DefineConstructor(
                attribs, CallingConventions.HasThis, args);

            ILGenerator il = builder.GetILGenerator();

            ctor.Info = builder;
            ctor.ILGenerator = il;

            if (tb.BaseType == typeof(SsLib.SimProg) || tb.BaseType == typeof(SsLib.SimObject))
            {
                //Call base class constructor
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Call, tb.BaseType.GetConstructor(Type.EmptyTypes));
            }
            else
            {
                //call base class constructor

                CtorSymbol baseCtor = definingType.BaseType.GetCtor();
                il.Emit(OpCodes.Ldarg_0);
                for (int i = 0; i < baseCtor.Args.Count; i++)
                    il.Emit(OpCodes.Ldarg_S, (byte) (i + 1));
                il.Emit(OpCodes.Call, baseCtor.Info);
            }

            //set field valus (and generate them as necesary
            if (args.Length > 0)
            {
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldarg_1);
                il.Emit(OpCodes.Stfld, ctor.Scope.ResolveField("_outer").Info);
            }

            //foreach (ArgumentSymbol arg in ctor.Args)
            //{

            //    string name = arg.Name.Substring(1);   //remove '_' prefix

            //    FieldSymbol f = ctx.CurrentType.ResolveField(name);

            //    this.LoadThis();
            //    this.Load(arg);
            //    this.Store(f);
            //}

        }

        public void CreateMetadata(MethodSymbol m)
        {
            TypeSymbol definingType = m.Scope as TypeSymbol;
            if (definingType == null) throw new ArgumentException(string.Format("The method '{0}' is not scoped inside a Type", m.Name), "m");

            // build argument list
            Type[] args = new Type[m.Args.Count];

            foreach (ArgumentSymbol arg in m.Args)
            {
                //args start at adress 1 because of the THIS pointer, so we subtract it here.
                args[arg.Adr-1] = arg.SystemType;
            }

            //    while (arg != null && arg.kind == Symbol.Kind.Arg)
            //    {
            //        if (arg.nameparam) args[arg.adr] = arg.type.sysType.MakeByRefType();
            //        else args[arg.adr] = arg.type.sysType;
            //        //bugfix: adding 1 to arg.adr since all methods in SIMULA are instance methods
            //        //so the arg_0 is the this pointer, all method arguments follow arg_0.
            //        arg.adr++;
            //        arg = arg.next;
            //    }

            TypeBuilder tb = definingType.Builder;

            MethodAttributes attribs = MethodAttributes.Public;
            if (m.Virtual) attribs |= MethodAttributes.Virtual;

            MethodBuilder builder = tb.DefineMethod(m.Name,
                attribs,// | MethodAttributes.HideBySig, 
                //CallingConventions.Standard, 
                m.SystemType, 
                args);


            //foreach (ParameterInfo pi in builder.GetParameters())
            //{
            //    ArgumentSymbol arg = m.GetArgument(pi.Position-1);
            //    arg.Info = pi;
            //}

            //MethodBuilder builder = program.DefineMethod(sym.name, 
            //    MethodAttributes.Public, sym.type.sysType, null);

            //while (arg != null && arg.kind == Symbol.Kind.Arg)
            //{
            //    arg.adr++;
            //   // builder.d
            //    if (arg.nameparam) builder.DefineParameter(arg.adr, ParameterAttributes.Out, arg.name);
            //    else builder.DefineParameter(arg.adr, ParameterAttributes.None, arg.name);
            //}

            ILGenerator il = builder.GetILGenerator();
            m.Info = builder;
            m.ILGenerator = il;
        }

        public void WritePEFile()
        {
            MethodBuilder mb = program.DefineMethod("EntryPoint", MethodAttributes.Static, typeof(int), null);
            ILGenerator il = mb.GetILGenerator();
            
            TypeSymbol prog = symTab.TopScope.ResolveType("Program");

            LocalBuilder exception = il.DeclareLocal(typeof(Exception));
            LocalBuilder retVal = il.DeclareLocal(typeof(int));

            il.Emit(OpCodes.Ldc_I4_0);
            il.Emit(OpCodes.Stloc_S, retVal);

            il.BeginExceptionBlock();

            il.Emit(OpCodes.Newobj, prog.GetCtor().Info);
            il.EmitCall(OpCodes.Callvirt, typeof(SsLib.SimObject).GetMethod("Epilog"), null);

            //il.BeginExceptFilterBlock();

            il.BeginCatchBlock(typeof(Exception));
            //il.BeginCatchBlock(null);
            //il.BeginFaultBlock();

            il.Emit(OpCodes.Stloc_S, exception);
            
            il.Emit(OpCodes.Call, typeof(Console).GetProperty("Error").GetGetMethod());

            il.Emit(OpCodes.Ldloc_S, exception);
            il.Emit(OpCodes.Callvirt, typeof(Exception).GetMethod("ToString"));

            il.Emit(OpCodes.Callvirt, typeof(System.IO.TextWriter).GetMethod("WriteLine", new Type[] {typeof(string)}));
            
            il.Emit(OpCodes.Ldc_I4_M1);
            il.Emit(OpCodes.Stloc_S, retVal);

            il.EndExceptionBlock();

            il.Emit(OpCodes.Ldloc_S, retVal);
            il.Emit(OpCodes.Ret);

            assembly.SetEntryPoint(mb);

            CreateTypes(prog);
            //program.CreateType();
            
            //if (inner != null) inner.CreateType();
            assembly.Save(exeName + ".exe");
        }

        public void CreateTypes(TypeSymbol t)
        {
            t.Builder.CreateType(); 
            foreach (Symbol member in t.Members.Values)
            {
                TypeSymbol nestedType = member as TypeSymbol;
                if (nestedType != null) CreateTypes(nestedType);
            }

        }

        /// <summary>
        /// Creates a type object for the class. This is necessary for the type object to be loaded.
        /// All type object for classes in a module/assembly must be created before the assembly can be stored to disk.
        /// </summary>
        /// <param name="t">The type symbol of the class type to be created.</param>
        public void CreateType(TypeSymbol t)
        {
            TypeBuilder tb = t.Builder;
            tb.CreateType();
        }

        //ANTLR tree aware stuff down here.

        //public Symbol ExpressionIdentifier(ScopeSymbol scope, SimSharpTreeNode node)
        //{
        //    Symbol sym = scope.Resolve(node.Text);
        //    if (sym is VariableSymbol) return sym;
        //    if (sym is MethodSymbol)
        //    {
        //        InspectSymbol inspect = scope.GetInspectForSymbol(node.Text);
        //        if (inspect != null)
        //        {
        //            if (inspect.InspectedSymbol is FieldSymbol)
        //            {
        //                LoadThisPointer(scope);
        //                il = scope.GetILGenerator();
        //                Load(inspect.InspectedSymbol);
        //                return sym;
        //            }
        //            else throw new NotImplementedException("Unknown call mode for inspected identifier " + node.Text);
        //        }
        //    }

        //    return null;
        //}




        //Pure symbol handling from here and down

        private string CreateUniqueName(string name)
        {
            int i = 0;
            while (ctx.Scope.Resolve(name + i.ToString()) != null) i++;

            name += i.ToString();

            return name;
        }

        public VariableSymbol CreateTempVar(string name, TypeSymbol t)
        {
            name = CreateUniqueName(name);

            VariableSymbol tempVar = ctx.Scope.AddVariable(name, t);
            this.CreateMetadata(tempVar);

            return tempVar;
        }

        /// <summary>
        /// Creates a local variable in the current method scope as an int vector with the specified size.
        /// An int vector is basicalle a zero-based one dimentional array of ints.
        /// </summary>
        /// <param name="size">Size of the int vector to create.</param>
        /// <returns>A VariableSymbol of the created int vector.</returns>
        public VariableSymbol CreateIntVector(int size)
        {
            VariableSymbol vector = this.CreateTempVar("intvector", SymbolTable.Integer.GetVectorType());
            this.LoadConst(size);
            ctx.IL.Emit(OpCodes.Newarr, typeof(int));
            this.Store(vector);
            return vector;
        }

        /// <summary>
        /// Creates an instance of an array with spesified element type, dimention sizes and start indices.
        /// </summary>
        /// <param name="elementType">Array element type.</param>
        /// <param name="sizeVector">Vector of dimention sizes.</param>
        /// <param name="indexStartVector">Vector of dimention start indices.</param>
        public void CreateArray(TypeSymbol elementType, VariableSymbol sizeVector, VariableSymbol indexStartVector)
        {
            Type[] types = new Type[3];
            types[0] = typeof(Type);
            types[1] = typeof(int).MakeArrayType();
            types[2] = typeof(int).MakeArrayType();

            MethodInfo m = typeof(Array).GetMethod("CreateInstance", types);

            this.LoadType(elementType);
            this.Load(sizeVector);
            this.Load(indexStartVector);
            this.CallMeth(m);
        }

        public InspectSymbol Connect(TypeSymbol inspectType)
        {
            string connectId = this.CreateUniqueName("_connect");

            VariableSymbol inspectedSym = ctx.Scope.AddVariable(connectId, inspectType);
            this.CreateMetadata(inspectedSym);

            InspectSymbol connectScope = ctx.Scope.AddInspect(inspectedSym);

            //Console.WriteLine("Connection: " + connectScope.Name);
            this.RefAssign(inspectedSym, inspectType);

            return connectScope;
        }

        public LabelSymbol DefineJumpPoint()
        {
            Label lab = ctx.IL.DefineLabel();
            LabelSymbol ls = new LabelSymbol("noname", lab);
            ls.Scope = ctx.Scope;  //TODO: verify that we need to track the enclosing scope.
            return ls;
        }

        public LabelSymbol BranchIfNull(Symbol symbolToTest)
        {
            ILGenerator il = ctx.IL;

            Label l1 = il.DefineLabel();
            this.Load(symbolToTest);
            il.Emit(OpCodes.Ldnull);
            il.Emit(OpCodes.Beq, l1);

            return new LabelSymbol("_nulltest", l1); 
        }

        public LabelSymbol BranchIfFalse()
        {
            ILGenerator il = ctx.IL;
            Label l1 = il.DefineLabel();

            il.Emit(OpCodes.Brfalse, l1);

            return new LabelSymbol("_falsetest", l1);
        }

        public void BranchIfFalse(LabelSymbol jumpPoint)
        {
            ctx.IL.Emit(OpCodes.Brfalse, jumpPoint.Label);
        }

        public void BranchIfTrue(LabelSymbol jumpPoint)
        {
            ctx.IL.Emit(OpCodes.Brtrue, jumpPoint.Label);
        }

        public LabelSymbol GotoJumpPoint()
        {
            ILGenerator il = ctx.IL;
            Label l1 = il.DefineLabel();
            il.Emit(OpCodes.Br, l1);
            return new LabelSymbol("_branch", l1);
        }

        public void GotoJumpPoint(LabelSymbol labSym)
        {
            ctx.IL.Emit(OpCodes.Br, labSym.Label);
        }

        public LabelSymbol CreateAndSetJumpPoint()
        {
            return CreateAndSetJumpPoint("_branch");
        }

        public LabelSymbol CreateAndSetJumpPoint(string name)
        {
            ILGenerator il = ctx.IL;
            Label label = il.DefineLabel();
            il.MarkLabel(label);
            return new LabelSymbol(name, label);
        }

        public void DefineLabel(LabelSymbol labSym)
        {
            if (!labSym.Defined)
            {
                labSym.Label = ctx.IL.DefineLabel();
            }
        }

        /// <summary>
        /// Marks the label as a jump point in the current IL stream
        /// </summary>
        /// <param name="label">The label to mark.</param>
        public void SetJumpPoint(LabelSymbol label)
        {
            ctx.IL.MarkLabel(label.Label);
        }

        public void Sub1Imediate()
        {
            ctx.IL.Emit(OpCodes.Ldc_I4_1);
            ctx.IL.Emit(OpCodes.Sub);
        }

        public void JumpTable(Label[] jumpTable)
        {
            ctx.IL.Emit(OpCodes.Switch, jumpTable);
        }

        public void ReturnFromProc()
        {
            ctx.IL.Emit(OpCodes.Ret);
        }

        public void LoadThis()
        {
            ctx.IL.Emit(OpCodes.Ldarg_0);
        }

        public void LoadOuter()
        {
            FieldSymbol outer = ctx.CurrentType.ResolveField("_outer");
            this.LoadThis();
            this.Load(outer);
        }

        public void LoadThisOrInspect(Symbol sym)
        {
            //Console.WriteLine("LoadThisOrInspect");
            //Console.WriteLine("Symbol: " + sym.Name);
            //Console.WriteLine("Scope: " + scope.Name +"\n");

            //We dont load this or inspect base symbol for these types of symbols.
            if (sym is VariableSymbol) return;
            if (sym is LitteralSymbol) return;
            if (sym is ArgumentSymbol) return;

            if (!(sym is VariableSymbol))
            {
                ILGenerator il = ctx.IL;

                InspectSymbol inspect = ctx.Scope.GetInspectForSymbol(sym);

                if (inspect == null)
                {
                    il.Emit(OpCodes.Ldarg_0);
                }
                else
                {
                    if (inspect.InspectedSymbol is FieldSymbol)
                    {
                        il.Emit(OpCodes.Ldarg_0);
                        Load(inspect.InspectedSymbol);
                    }
                    else if (inspect.InspectedSymbol is VariableSymbol)
                    {
                        Load(inspect.InspectedSymbol);
                    }
                    else throw new NotImplementedException("Unknown call mode for inspected identifier " + sym.Name);
                }
            }
        }

        public void IncSymbol(Symbol s)
        {
            this.Load(s);
            this.LoadConst(1);
            this.ExprPlus();
            this.ValAssign(s, SymbolTable.Integer);
        }

        public void ValueRelation(TypeSymbol x, TypeSymbol y)
        {
            if (x != y) throw new Exception(string.Format("Incompatible types in ValueRelation '{0}:{1}'", 
                x.SystemType.ToString(), y.SystemType.ToString()));

            if (x == SymbolTable.Text)
            {
                this.CallMeth(typeof(SsLib.Text).GetMethod("Compare"));
                this.LoadConst(0);
            }
        }

        /// <summary>
        /// Performes an Imp (implies) test of the two boolean values currently on the eval stack.
        /// Returns false only in the case the first operand is true and the second operand is false:
        /// </summary>
        public void ExprImp()
        {
            Label labTrue = ctx.IL.DefineLabel();
            Label labEnd = ctx.IL.DefineLabel();
            ctx.IL.Emit(OpCodes.Brtrue_S, labTrue);
            ctx.IL.Emit(OpCodes.Ldc_I4_0);   //load false
            ctx.IL.Emit(OpCodes.Ceq);
            ctx.IL.Emit(OpCodes.Br_S, labEnd);
            ctx.IL.MarkLabel(labTrue);
            ctx.IL.Emit(OpCodes.Pop);
            ctx.IL.Emit(OpCodes.Ldc_I4_1);
            ctx.IL.MarkLabel(labEnd);
        }

        /// <summary>
        /// Performes an Eqv (equivalent to) test of the two boolean values currently on the eval stack.
        /// Returns true of both values are equal, returns false if they differ.
        /// </summary>
        public void ExprEqv()
        {
            ctx.IL.Emit(OpCodes.Ceq);
        }

        /// <summary>
        /// Performes an Or test of the two boolean values currently on the eval stack.
        /// Tests the second bool value. If true, pops the first value and loads true.
        /// If false lets the first value fall through as the result of the test.
        /// </summary>
        public void ExprOr()
        {
            Label labEnd = ctx.IL.DefineLabel();
            ctx.IL.Emit(OpCodes.Brfalse_S, labEnd);
            ctx.IL.Emit(OpCodes.Pop);
            ctx.IL.Emit(OpCodes.Ldc_I4_1);
            ctx.IL.MarkLabel(labEnd);
        }

        /// <summary>
        /// Performes an And test of the two boolean values currently on the eval stack.
        /// First tests the second bool value. If false, pops the first value and loads false.
        /// If true lets the first value fall through as the result of the test.
        /// </summary>
        public void ExprAnd()
        {
            Label labEnd = ctx.IL.DefineLabel();
            ctx.IL.Emit(OpCodes.Brtrue_S, labEnd);
            ctx.IL.Emit(OpCodes.Pop);
            ctx.IL.Emit(OpCodes.Ldc_I4_0);
            ctx.IL.MarkLabel(labEnd);
        }

        public void ExprIn(TypeSymbol t)
        {
            ctx.IL.Emit(OpCodes.Isinst, t.SystemType);
        }

        public void ExprIs(TypeSymbol t)
        {
            this.LoadType(t);
            this.CallMeth(typeof(object).GetMethod("ReferenceEquals"));
        }

        public void ExprEq()
        {
            ctx.IL.Emit(OpCodes.Ceq);
        }

        public void ExprNe()
        {
            //scope.GetILGenerator().Emit(OpCodes.Not);
            ILGenerator il = ctx.IL;
            il.Emit(OpCodes.Ceq);
            il.Emit(OpCodes.Ldc_I4_0);
            il.Emit(OpCodes.Ceq);
        }

        public void ExprGt()
        {
            ctx.IL.Emit(OpCodes.Cgt);
        }

        public void ExprLt()
        {
            ctx.IL.Emit(OpCodes.Clt);
        }

        public void ExprGe()
        {
            ILGenerator il = ctx.IL;
            il.Emit(OpCodes.Clt);
            il.Emit(OpCodes.Ldc_I4_0);
            il.Emit(OpCodes.Ceq);
        }

        public void ExprLe()
        {
            ILGenerator il = ctx.IL;
            il.Emit(OpCodes.Cgt);
            il.Emit(OpCodes.Ldc_I4_0);
            il.Emit(OpCodes.Ceq);
        }

        public void ExprMinus()
        {
            ctx.IL.Emit(OpCodes.Sub);
        }

        public void ExprPlus()
        {
            ctx.IL.Emit(OpCodes.Add);
        }

        public void ExprTimes()
        {
            ctx.IL.Emit(OpCodes.Mul);
        }

        public void ExprIntDiv()
        {
            ctx.IL.Emit(OpCodes.Div);
            ctx.IL.Emit(OpCodes.Conv_I4);
        }

        public void ExprExp()
        {
            ctx.IL.Emit(OpCodes.Call, typeof(System.Math).GetMethod("Pow"));
        }

        public void ExprNot()
        {
            ILGenerator il = ctx.IL;
            il.Emit(OpCodes.Ldc_I4_0);
            il.Emit(OpCodes.Ceq);
        }

        public void ExprNeg()
        {
            ctx.IL.Emit(OpCodes.Neg);
        }

        public void ExprConcatText()
        {
            ctx.IL.Emit(OpCodes.Call, typeof(SsLib.Text).GetMethod("ConCat"));
        }

        public void Pop()
        {
            ctx.IL.Emit(OpCodes.Pop);
        }

        public void Dup()
        {
            ctx.IL.Emit(OpCodes.Dup);
        }

        public void CallMethod(MethodSymbol m)
        {
            if (m.Info == null) throw new ArgumentException("Missing method info on method '" + m.Name + "'.", "m");
            CallMeth(m.Info);
        }

        public void CallBase(MethodSymbol m)
        {

            TypeSymbol t = m.Scope as TypeSymbol;
            MethodInfo meth = t.BaseType.ResolveMethod(m.Name).Info;
            ctx.IL.Emit(OpCodes.Call, meth);
        }

        private void CallMeth(MethodInfo meth)
        {
            if (meth.IsVirtual) ctx.IL.Emit(OpCodes.Callvirt, meth);
            else ctx.IL.Emit(OpCodes.Call, meth);
        }

        public void MakeValueParam(ArgumentSymbol arg)
        {
            ctx.IL.Emit(OpCodes.Ldarg_0);           //this pointer
            this.LoadAsObject(arg);
            this.CallMeth(SymbolTable.Text.ResolveMethod("Length").Info);
            this.CallMeth(ctx.Scope.ResolveMethod("Blanks").Info);


            VariableSymbol temp = ctx.Scope.AddVariable("_"+arg.Name, SymbolTable.Text);
            CreateMetadata(temp);

            this.Store(temp);
            this.LoadAdress(temp);
            this.Load(arg);
            this.CallMeth(SymbolTable.Text.ResolveMethod("SetValue").Info);

            //il.Emit(OpCodes.Unbox, SymbolTable.Text.SystemType);

            this.Load(temp);
            this.Store(arg);
        }

        public void ValAssign(Symbol dest, TypeSymbol sourceType)
        {
            //Test to check if this is an array element assignment
            if (dest.Type.IsArray && dest == dest.Type)
            {
                TypeConvert(sourceType, dest.Type.ElementType);
                //dest = dest.Type;
            }
            else TypeConvert(sourceType, dest.Type);
            Store(dest);
        }

        public void RefAssign(Symbol dest, TypeSymbol sourceType)
        {
            //Test to check if this is an array element assignment
            if (dest.Type.IsArray && dest == dest.Type)
            {
                TypeConvert(sourceType, dest.Type.ElementType);
                //dest = dest.Type;
            }
            else TypeConvert(sourceType, dest.Type);
            Store(dest);
        }


        /// <summary>
        /// Creates a new object instance of a class. The new object constitutes a new Coroutine.
        /// </summary>
        /// <remarks>
        /// Creates a new object instance of a class. The new object constitutes a new Coroutine.
        /// The object instance is first created using the IL NewObj operator. The new object is stored in a temporary local variable.
        /// Then we launch a new Coroutine using SimObject.LaunchCoroutine method, which takes the newly created object instance as a parameter.
        /// The LaunchCoroutine is responsible for executing the main block of the object (Prolog/Epilog) on a new thread.
        /// The Coroutine is attached to the object (class block) that created the instance in the first place.
        /// 
        /// This is the main reason why we need Prolog as a separate method from the object constructor in the first place.
        /// We need to be able to use the object independantly of the coroutine executing the objects class block.
        /// 
        /// The newly constructed object instance is returned to the caller (on top of eval stack) independantly of the executing coroutine.
        /// </remarks>
        /// <param name="ctor">The constructor symbol of the object instance to create.</param>
        public void NewObject(CtorSymbol ctor)
        {
            VariableSymbol temp = this.CreateTempVar("_newinst", ctor.Scope as TypeSymbol);

            ctx.IL.Emit(OpCodes.Newobj, ctor.Info);
            this.Store(temp);
            this.Load(temp);
            this.CallMeth(typeof(SsLib.SimObject).GetMethod("LaunchCoroutine"));
            this.Load(temp);
        }

        public void LoadNull()
        {
            ctx.IL.Emit(OpCodes.Ldnull);
        }

        /// <summary>
        /// Loads the Type of the specified TypeSymbol to the eval stack.
        /// This is done by first loading the type token and then converting it to a type.
        /// </summary>
        /// <param name="elementType"></param>
        public void LoadType(TypeSymbol typeSymbol)
        {
            ctx.IL.Emit(OpCodes.Ldtoken, typeSymbol.SystemType);
            this.CallMeth(typeof(Type).GetMethod("GetTypeFromHandle"));
        }

        public void LoadObjectType()
        {
            this.CallMeth(typeof(object).GetMethod("GetType"));
        }

        public void LoadAsObject(Symbol source)
        {

            if (source is TypeSymbol && source.Type.IsArray)
            {
                int dims = source.Type.Dimentions;
                Type[] types = new Type[dims];
                for (int i = 0; i < dims; i++) types[i] = typeof(int);

                MethodInfo mi = null;
                if (source.Type.ElementType.Builder == null)
                {
                    mi = source.SystemType.GetMethod("Address", types);
                    this.CallMeth(mi);
                }
                else
                {
                    //Shity hack to get around the stupid reflection.emit restriction of only handling created types 
                    //in EmitCall with method info to runtime generated Set methods.
                    //This hack is only implemented for single dimention arrays for now.
                    Type t = Type.GetType("System.Object[*]");
                    mi = t.GetMethod("Address");
                    this.CallMeth(mi);
                }
            }
            else
            {
                //things that allready is an object we just load normally.
                Type t = source.SystemType;
                if (t.IsByRef || t.IsClass) Load(source);
                else LoadAdress(source);
            }
        }

        private void LoadAdress(Symbol sym)
        {
            if (sym is FieldSymbol) LoadAdress((FieldSymbol)sym);
            else if (sym is VariableSymbol) LoadAdress((VariableSymbol)sym);
            else if (sym is ArgumentSymbol) LoadAdress((ArgumentSymbol)sym);
            else throw new NotImplementedException("We have not implemented support for loading address of " + sym.GetType().Name);
        }

        public void LoadByRef(Symbol sym)
        {
            if (sym is VariableSymbol) LoadAdress((VariableSymbol)sym);
            else if (sym is FieldSymbol) LoadAdress((FieldSymbol)sym);
            else throw new NotImplementedException("We have not implemented support for loading ByRef of " + sym.GetType().Name);

        }

        public void Load(Symbol sym)
        {
            if (sym is LitteralSymbol) Load((LitteralSymbol)sym);
            else if (sym is FieldSymbol) Load((FieldSymbol)sym);
            else if (sym is VariableSymbol) Load((VariableSymbol)sym);
            else if (sym is ArgumentSymbol) Load((ArgumentSymbol)sym);
            
            //loading an array element:
            else if (sym is TypeSymbol && sym.Type.IsArray)
            {
                int dims = sym.Type.Dimentions;
                Type[] types = new Type[dims];
                for (int i = 0; i < dims; i++) types[i] = typeof(int);

                MethodInfo mi = null;
                if (sym.Type.ElementType.Builder == null)
                {
                    mi = sym.SystemType.GetMethod("Get", types);
                    this.CallMeth(mi);
                }
                else
                {
                    //Shity hack to get around the stupid reflection.emit restriction of only handling created types 
                    //in EmitCall with method info to runtime generated Set methods.
                    //This hack is only implemented for single dimention arrays for now.
                    Type t = Type.GetType("System.Object[*]");
                    mi = t.GetMethod("Get");
                    this.CallMeth(mi);
                }
            }
            else throw new NotImplementedException("We have not implemented support for loading " + sym.GetType().Name);
        }

        private void LoadAdress(ArgumentSymbol sym)
        {
            int adr = sym.Adr;
            if (adr >= 0 && adr <= 255) ctx.IL.Emit(OpCodes.Ldarga_S, (byte)adr);
            else ctx.IL.Emit(OpCodes.Ldarga, adr);
        }

        private void Load(ArgumentSymbol sym)
        {
            switch (sym.Adr)
            {
                case 0: ctx.IL.Emit(OpCodes.Ldarg_0); break;
                case 1: ctx.IL.Emit(OpCodes.Ldarg_1); break;
                case 2: ctx.IL.Emit(OpCodes.Ldarg_2); break;
                case 3: ctx.IL.Emit(OpCodes.Ldarg_3); break;
                default:
                    if (sym.Adr > 255)
                        ctx.IL.Emit(OpCodes.Ldarg, sym.Adr);
                    else
                        ctx.IL.Emit(OpCodes.Ldarg_S, (byte)sym.Adr); 
                    break;
            }
        }

        private void LoadAdress(VariableSymbol sym)
        {
            int adr = sym.Info.LocalIndex;
            if (adr >= 0 && adr <= 255) ctx.IL.Emit(OpCodes.Ldloca_S, (byte)adr);
            else ctx.IL.Emit(OpCodes.Ldloca, adr);
        }

        private void Load(VariableSymbol sym)
        {
            switch (sym.Info.LocalIndex)
            {
                case 0: ctx.IL.Emit(OpCodes.Ldloc_0); break;
                case 1: ctx.IL.Emit(OpCodes.Ldloc_1); break;
                case 2: ctx.IL.Emit(OpCodes.Ldloc_2); break;
                case 3: ctx.IL.Emit(OpCodes.Ldloc_3); break;
                default:
                    if (sym.Info.LocalIndex > 255)
                        ctx.IL.Emit(OpCodes.Ldloc, sym.Info.LocalIndex);
                    else
                        ctx.IL.Emit(OpCodes.Ldloc_S, (byte)sym.Info.LocalIndex); 
                    break;
            }
        }

        private void LoadAdress(FieldSymbol sym)
        {
            ctx.IL.Emit(OpCodes.Ldflda, sym.Info);
        }

        private void Load(FieldSymbol sym)
        {
            if (sym.Info.IsStatic) ctx.IL.Emit(OpCodes.Ldsfld, sym.Info);
            else ctx.IL.Emit(OpCodes.Ldfld, sym.Info);
        }

        /* Load the operand x onto the expression stack. */
        private void Load(LitteralSymbol sym)
        {
            switch (sym.kind)
            {
                case LitteralSymbol.Kind.INT:
                    LoadConst((int)sym.value);
                    break;
                case LitteralSymbol.Kind.CHAR:
                    LoadConst((char)sym.value);
                    break;
                case LitteralSymbol.Kind.REAL:
                    LoadConst((float)sym.value);
                    break;
                case LitteralSymbol.Kind.STRING:
                    LoadConst((string)sym.value);
                    break;
                case LitteralSymbol.Kind.BOOL:
                    LoadConst(Convert.ToInt32(sym.value));
                    break;
                default:
                    throw new NotImplementedException("Not implemented constant loading for litteral type '" + sym.kind.ToString() + "'.");
            }
        }

        /* Load an integer constant onto the expression stack. */
        public void LoadConst(int n)
        {
            switch (n)
            {
                case -1: ctx.IL.Emit(OpCodes.Ldc_I4_M1); break;
                case 0: ctx.IL.Emit(OpCodes.Ldc_I4_0); break;
                case 1: ctx.IL.Emit(OpCodes.Ldc_I4_1); break;
                case 2: ctx.IL.Emit(OpCodes.Ldc_I4_2); break;
                case 3: ctx.IL.Emit(OpCodes.Ldc_I4_3); break;
                case 4: ctx.IL.Emit(OpCodes.Ldc_I4_4); break;
                case 5: ctx.IL.Emit(OpCodes.Ldc_I4_5); break;
                case 6: ctx.IL.Emit(OpCodes.Ldc_I4_6); break;
                case 7: ctx.IL.Emit(OpCodes.Ldc_I4_7); break;
                case 8: ctx.IL.Emit(OpCodes.Ldc_I4_8); break;
                default:
                    if (n >= -127 && n <= 128) ctx.IL.Emit(OpCodes.Ldc_I4_S, (byte)n);
                    else ctx.IL.Emit(OpCodes.Ldc_I4, n);
                    break;
            }
        }

        private void LoadConst(float f)
        {
            ctx.IL.Emit(OpCodes.Ldc_R4, f);
        }

        private void LoadConst(string s)
        {
            ctx.IL.Emit(OpCodes.Ldstr, s);
            ctx.IL.Emit(OpCodes.Call, typeof(SsLib.Text).GetMethod("op_Implicit"));
        }

        public Symbol ConvertToObject(TypeSymbol t)
        {
            object r = this.ctx.PopRemotes();  //we might be in wrong scope because of remote access. (as in simsharpgen.id)

            if (t.SystemType.IsClass) return null;
          
            int i = 0;
            string tempId = "_loctemp";
            while (ctx.Scope.Resolve(tempId+i.ToString()) != null) i++;

            VariableSymbol v = ctx.Scope.AddVariable(tempId + i.ToString(), t);
            this.CreateMetadata(v);

            this.Store(v);
            this.LoadAdress(v);

            this.ctx.PushRemotes(r);

            return v;
        }

        //public void TypeConvert(ScopeSymbol scope, TypeSymbol t)
        //{
        //    switch (t.SystemType.Name)
        //    {
        //        case "Single":
        //            il.Emit(OpCodes.Conv_R4);
        //            break;
        //        case "Double":
        //            il.Emit(OpCodes.Conv_R8);
        //            break;
        //        default:
        //            throw new NotImplementedException("Type convertion to " + t.SystemType.Name +
        //                " is not implemented.");
        //    }
        //}


        private bool IsArithmetic(TypeSymbol t)
        {
            if (t == SymbolTable.Integer) return true;
            if (t == SymbolTable.Long) return true;
            if (t == SymbolTable.Real) return true;
            if (t == SymbolTable.Short) return true;
            return false;
        }

        public bool TypeConvert(TypeSymbol source, TypeSymbol dest)
        {
            Debug.Assert(source != null, "TypeConvert: Source TypeSymbol is null.");
            Debug.Assert(dest != null, "TypeConvert: Dest TypeSymbol is null.");
            Debug.Assert(source.SystemType != null, "TypeConvert: Source '" + source.Name + "' SystemType is null.");
            Debug.Assert(dest.SystemType != null, "TypeConvert: Dest '" + dest.Name + "' SystemType is null.");
            
            if (source.SystemType == dest.SystemType) return true;

            switch (dest.SystemType.Name)
            {
                case "Single":
                    if (IsArithmetic(source.Type))
                    {
                        ctx.IL.Emit(OpCodes.Conv_R4);
                        return true;
                    }
                    return false;
                //case DataType.Kind.LongReal:
                //    il.Emit(OpCodes.Conv_R8);
                //    codeItem.type = DataType.LongReal;
                //    break;
                case "Int32":
                    if (source.SystemType == typeof(float))
                    {
                        MethodInfo mi = typeof(Convert).GetMethod("ToInt32", new Type[] { typeof(float) });
                        this.CallMeth(mi);
                    }
                    else ctx.IL.Emit(OpCodes.Conv_Ovf_I4);
                    return true;
                case "Int16":
                    if (source.SystemType == typeof(int))
                    {
                        ctx.IL.Emit(OpCodes.Conv_Ovf_I2);
                        return true;
                    }
                    break;
                case "Double":
                    if (IsArithmetic(source.Type))
                    {
                        ctx.IL.Emit(OpCodes.Conv_R8);
                        return true;
                    }
                    else return false;
                default:
                    //Type convertion where source is a subclass of the destionation us totally safe
                    //and does not require any cast.
                    if (source.SystemType.IsSubclassOf(dest.SystemType))
                    {
                        return true;  //No type convertion is necessary.
                    }
                    
                    //Type convertion where destination is a subclass of the source must be type cast.
                    //And might throw an exception at runtime.
                    if (dest.SystemType.IsSubclassOf(source.SystemType))
                    {
                        ctx.IL.Emit(OpCodes.Castclass, dest.SystemType);
                        return true;
                    }

                    if (source.SystemType.IsByRef)
                    {
                        Type deRef = source.SystemType.GetElementType();
                        if (deRef == dest.SystemType)
                        {
                            ctx.IL.Emit(OpCodes.Ldind_Ref);
                            return true;
                        }
                    }
                    if (dest.SystemType.IsByRef)
                    {
                        Type deRef = dest.SystemType.GetElementType();
                        if (deRef == source.SystemType)
                        {
                            //ctx.IL.Emit(OpCodes.Refanyval, source.SystemType);
                            //just return true, the adress translation is handled elsewere (I hope), typically in Store methods.
                            return true;
                        }
                    }
                
                    //TODO: implement type convertion for user defined types.
                    break;
            }
            throw new NotImplementedException("Type convertion from " + source.SystemType.Name +
                " to " + dest.SystemType.Name + " is not implemented.");

            //return false;
        }

        /// <summary>
        /// Stores an item from eval stack into a vector.
        /// Vector reference, index and item to be stored must be on eval stack.
        /// </summary>
        /// <param name="t">Type of the item to be stored.</param>
        public void StoreVectorElement(TypeSymbol t)
        {
            ctx.IL.Emit(OpCodes.Stelem, t.SystemType);
        }

        /// <summary>
        /// Loads an element item from a vector into the eval stack.
        /// </summary>
        /// <param name="s">Symbol holding the vector.</param>
        /// <param name="index">Index of element to load.</param>
        public void LoadVectorElement(Symbol s, int index)
        {
            this.Load(s);
            this.LoadConst(index);
            ctx.IL.Emit(OpCodes.Ldelem, s.Type.ElementType.SystemType);
        }


        public void Store(Symbol destination)
        {
            if (destination is VariableSymbol) Store((VariableSymbol)destination);
            else if (destination is FieldSymbol) Store((FieldSymbol)destination);
            else if (destination is ArgumentSymbol) Store((ArgumentSymbol)destination);

            //storing to an array element:
            else if (destination is TypeSymbol && destination.Type.IsArray)
            {
                int dims = destination.Type.Dimentions;
                Type[] types = new Type[dims + 1];
                for(int i = 0; i < dims; i++) types[i] = typeof(int);
                types[dims] = destination.Type.ElementType.SystemType;


                MethodInfo mi = null;
                if (destination.Type.ElementType.Builder == null)
                {
                    mi = destination.SystemType.GetMethod("Set", types);
                    this.CallMeth(mi);
                }
                else
                {
                    //object o = Activator.CreateInstance(destination.Type.SystemType);
                    //mi = destination.Type.ElementType.Builder.CreateType().MakeArrayType(dims).GetMethod("Set", types);


                    //Shity hack to get around the stupid reflection.emit restriction of only handling created types 
                    //in EmitCall with method info to runtime generated Set methods.
                    //This hack is only implemented for single dimention arrays for now.
                    Type t = Type.GetType("System.Object[*]");
                    mi = t.GetMethod("Set");

                    //mi = destination.Type.ElementType.Builder.MakeArrayType(dims).GetMethod("Set", types);
                    ctx.IL.Emit(OpCodes.Call, mi);
                }
                //if (mi == nul

            }
            
            else throw new NotImplementedException("We have not implemented support for storing to " + destination.GetType().Name);
        }

        private void Store(FieldSymbol f)
        {
            ctx.IL.Emit(OpCodes.Stfld, f.Info);
        }

        private void Store(ArgumentSymbol arg)
        {
            if (arg.NameParam)
            {
                if (arg.Type.GetDeRefType() == SymbolTable.Text)
                {
                    ctx.IL.Emit(OpCodes.Stobj, arg.Type.SystemType.GetElementType());  //arg.SystemType.);
                }
                else if (arg.Type.SystemType.GetElementType().IsSubclassOf(typeof(SsLib.SimObject)))
                {
                    ctx.IL.Emit(OpCodes.Stobj, arg.Type.SystemType.GetElementType());
                }
                else throw new NotImplementedException("Store Argument ByRef for " + arg.Type.Name + " is not implemeneted yet.");

            }
            else
            {
                if (arg.Adr > 255) ctx.IL.Emit(OpCodes.Starg, arg.Adr);
                else ctx.IL.Emit(OpCodes.Starg_S, (byte)arg.Adr);
            }
        }

        private void Store(VariableSymbol x)
        {

            //switch (x.adr) {
            //bugfix: see Symbol and CodeGen.CreateMetadata
            switch (x.Info.LocalIndex)
            {
                case 0: ctx.IL.Emit(OpCodes.Stloc_0); break;
                case 1: ctx.IL.Emit(OpCodes.Stloc_1); break;
                case 2: ctx.IL.Emit(OpCodes.Stloc_2); break;
                case 3: ctx.IL.Emit(OpCodes.Stloc_3); break;
                default:
                    if (x.Info.LocalIndex > 255)
                        ctx.IL.Emit(OpCodes.Stloc, x.Info.LocalIndex);
                    else
                        ctx.IL.Emit(OpCodes.Stloc_S, (byte) x.Info.LocalIndex); 
                    break;

            }
        }
    }
}
